package ddbserver.optimizer;

/**
*
* @author LH
*/

import java.awt.Font;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.Vector;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.swing.JComponent;
import javax.swing.JFrame;

import ddbserver.common.GDDNode;
import ddbserver.common.Table;
import ddbserver.connections.GDDManager;
import ddbserver.connections.TableManager;
import ddbserver.constant.Constant;

public class QueryTree {
	TreeNode root;
	Vector<String> pa; // attributes for projection
	Vector<String> joins; //predicates for joining
	Vector<String> selects; //predicates for selection
	Vector<String> tables; //store table names
	ArrayList<TreeNode> globelTable;
	int xstep = 20;
	int ystep = 30;
	int ypos = 10;
	String op = "(<=)|(>=)|(<>)|(<)|(>)|(=)";
	
	public QueryTree() {
		pa = new Vector<String>();
		joins = new Vector<String>();
		selects = new Vector<String>();
		tables = new Vector<String>();
		globelTable = new ArrayList<TreeNode>();
	}
	
	public void addAttribute(String e) {
		pa.add(e);
	}
	
	public void addJoin(String e) {
		joins.add(e);
	}
	
	public void addSelect(String e) {
		selects.add(e);
	}
	
	public void addTable(String e) {
		tables.add(e);
	}

    public Vector<String> getTable() {
        return tables;
    }

    public Vector<String> getJoins() {
        return joins;
    }

    public Vector<String> getAttribute() {
        return pa;
    }
	
	private void printNode(TreeNode e, Graphics g, int x) {
		String type = e.getType();
		String line = type+"( ";
		if(type.equals(Constant.DDB_LABEL_PROJECT)) {
			for(String s : e.getAttribute()) {
				if(s.indexOf(".")!=-1) {
					s = s.split("\\.", 2)[1];
				}
				line = line+s+" ";
			}
		}else if(type.equals(Constant.DDB_LABEL_SELECT)) {
			for(String s : e.getPredicate()) {
				line = line+s+" ";
			}
		}else if(type.equals(Constant.DDB_LABEL_JOIN)) {
			for(String s : e.getPredicate()) {
				if(s.indexOf("=")!=-1) {
					String one = s.split("=", 2)[0].trim();
					s = one.split("\\.", 2)[1].trim();
				}
				line = line+s+" ";
			}
		}
		else {
			//assert(type.equals(Constant.DDB_LABEL_SITE));
			line = line+e.getName()+" ";
		}
		line = line+")";
		g.drawString(line, x, ypos);
	}
	
	private void printTree(TreeNode root, Graphics g, Integer x) {
		printNode(root, g, x);
		Vector<TreeNode> children = root.getChild();
		for(TreeNode child : children) {
			ypos += ystep;
			printTree(child, g, x+xstep);
		}
	}
	
	private TreeNode parseGDDTree(GDDNode root, String tablename, 
			Stack<String> predicates) {
		TreeNode tnode = new TreeNode();
		if(root.isIsLeaf()) {
			tnode.setType(Constant.DDB_LABEL_SITE);
			tnode.setName(root.getLocation().getName()+"_"+tablename);
			for(int i=predicates.size()-1;i>=0;i--) {
				tnode.addPredicate(predicates.get(i));
			}
		}else {
			String ftype = root.getFragmentType();
			if(ftype.equals(Constant.DDB_FRAGMENT_HORIZONATALLY)) {
				tnode.setType(Constant.DDB_LABEL_UNION);
				List<String> pds = root.getPredicate();
				String attribute = pds.get(0).split(op, 2)[0].trim();
				//name of union is the fragmenting attribute
				tnode.setName(attribute);
				List<GDDNode> children = root.getSons();
				for(int i=0;i<pds.size();i++) {
					String pd = pds.get(i);
					predicates.push(pd);
					tnode.addPredicate(pd);
					TreeNode child = 
						parseGDDTree(children.get(i), tablename, predicates);
					predicates.pop();
					tnode.addChild(child);
					child.setParent(tnode);
				}
			}else if(ftype.equals(Constant.DDB_FRAGMENT_VERTICALLLY)) {
				//So now join has two type:global-join and vertical-fragment-join
				tnode.setType(Constant.DDB_LABEL_JOIN);
				/*Every vertical-fragment-join TreeNode has a name denoted its
				belonging global table*/
				tnode.setName(tablename);
				List<String> frags = root.getPredicate();
				List<GDDNode> children = root.getSons();
				HashMap<String, Integer> map = new HashMap<String, Integer>();
				for(String frag : frags) {
					String[] attris = frag.split("\\s");
					for(String ar : attris) {
						Integer n = map.get(ar);
						if(n == null) {
							map.put(ar, 1);
						}else {
							map.put(ar, n+1);
						}
					}
				}
				Set<String> keys = map.keySet();
				for(String key : keys) {
					if(map.get(key) == children.size()) {
						tnode.addAttribute(key);
					}
				}
				for(int i=0;i<frags.size();i++) {
					String frag = frags.get(i);
					predicates.push(frag);
					TreeNode child = 
						parseGDDTree(children.get(i),tablename,predicates);
					predicates.pop();
					tnode.addChild(child);
					tnode.addPredicate(frag);
					child.setParent(tnode);
				}
			}
		}
		return tnode;
	}
	
	private TreeNode constructSubTree(String tablename) {
		GDDManager gddm = GDDManager.getInstance();
		GDDNode gddRoot = gddm.getGDDNodeByTableName(tablename);
		Stack<String> predicates = new Stack<String>();
		return parseGDDTree(gddRoot, tablename, predicates);
	}
	
	public TreeNode original() {
		root = new TreeNode();
		root.setType(Constant.DDB_LABEL_PROJECT);
		if(pa.size() == 1 && pa.get(0).trim().equals("*")) {
			TableManager tmanager = TableManager.getInstance();
			for(String tn : tables) {
				Table t = tmanager.getTable(tn);
				for(String col : t.getCols()) {
					root.addAttribute(tn+"."+col.trim());
				}
			}
		}else {
			for(String attribute : pa) {
				root.addAttribute(attribute);
			}
		}
		TreeNode pnode = new TreeNode();
		pnode.setType(Constant.DDB_LABEL_SELECT);
		pnode.setParent(root);
		for(String select : selects) {
			pnode.addPredicate(select);
		}
		ArrayList<TreeNode> nlist = new ArrayList<TreeNode>();
		for(String tn : tables) {
			TreeNode node = constructSubTree(tn);
			node.setName(tn);
			node.addJoinTable(tn);
			nlist.add(node);
			globelTable.add(node);
		}
		//Global joins(having no name)
		for(String join : joins) {
			String[] lr = join.split("=", 2);
			TreeNode newnode = new TreeNode();
			newnode.setType(Constant.DDB_LABEL_JOIN);
			newnode.addPredicate(join);
			for(int i=0;i<2;i++) {
				String tablename = lr[i].split("\\.",2)[0].trim();
				for(int j=0;j<nlist.size();j++) {
					if(nlist.get(j).isJoinTable(tablename)) {
						TreeNode n = nlist.get(j);
						newnode.addChild(n);
						n.setParent(newnode);
						for(String jtn : n.getJoinTable()) {
							newnode.addJoinTable(jtn);
						}
						nlist.remove(j);
						break;
					}
				}
			}
			nlist.add(newnode);
		}
		TreeNode jnode = nlist.get(0);
		pnode.addChild(jnode);
		jnode.setParent(pnode);
		root.addChild(pnode);
		pnode.setParent(root);
		return root;
	}
	
	private boolean conflict(String range, String fragment, String tablename) {
		String[] lr1 = range.split(op, 2);
		String[] lr2 = fragment.split(op, 2);
		for(int i=0;i<2;i++) {
			lr1[i] = lr1[i].trim();
			lr2[i] = lr2[i].trim();
		}
		String op1 = range.substring(lr1[0].length(), range.indexOf(lr1[1]));
		String op2 = fragment.substring(lr2[0].length(), fragment.indexOf(lr2[1]));
		
		if(lr1[0].indexOf(".")!=-1) {
			lr1[0] = lr1[0].split("\\.", 2)[1].trim();
		}
		if(lr1[0].equalsIgnoreCase(lr2[0])) {
			TableManager tmanager = TableManager.getInstance();
			Table gtable= tmanager.getTable(tablename);
			String type = gtable.getType(lr1[0]).toLowerCase();
			if(type.indexOf("char")==-1 && type.indexOf("varchar")==-1) {
				int rvalue = Integer.parseInt(lr1[1]);
				int fvalue = Integer.parseInt(lr2[1]);
				if(op1.equals("<>")) {
					if(rvalue == fvalue && op2.equals("=")) {
						return true;
					}else {
						return false;
					}
				}
				if(rvalue < fvalue) {
					if( (op1.equals("<=")||op1.equals("<")||op1.equals("="))
							&&
						(op2.equals(">")||op2.equals(">=")||(op2.equals("=")))) 
					{
						return true;
					}else {
						return false;
					}
				}else if(rvalue > fvalue) {
					if( (op1.equals(">=")||op1.equals(">")||op1.equals("="))
							&&
						(op2.equals("<")||op2.equals("<=")||(op2.equals("=")))) 
					{
						return true;
					}else {
						return false;
					}
				}else {
					if( (op1.equals("<") && op2.equals(">=")) ||
							(op1.equals("<=") && op2.equals(">")) ||
							(op1.equals(">") && op2.equals("<=")) ||
							(op1.equals(">=") && op2.equals("<")) ||
							(op1.equals("<>") && op2.equals("=")) ||
							(op1.equals("=") && op2.equals("<>")) ) {
						return true;
					}else {
						return false;
					}
				}
			}else {
				String rvalue = lr1[1].trim();
				String fvalue = lr2[1].trim();
				if(rvalue.equalsIgnoreCase(fvalue)) {
					return false;
				}else {
					return true;
				}
			}
		}
		return false;
	}
	
	private boolean insertSelect(TreeNode sroot, Vector<String> pds, String tablename) {
		if(sroot.getType().equals(Constant.DDB_LABEL_SITE)) {
			TreeNode node = new TreeNode();
			node.setType(Constant.DDB_LABEL_SELECT);
			for(String pd : pds) {
				node.addPredicate(pd);
			}
			node.addChild(sroot);
			TreeNode parent = sroot.getParent();
			for(TreeNode son : parent.getChild()) {
				if(son == sroot) {
					son = node;
					break;
				}
			}
			sroot.setParent(node);
			node.setParent(parent);
		}else if(sroot.getType().equals(Constant.DDB_LABEL_UNION)) {
			Vector<String> hp = sroot.getPredicate();
			HashMap<Integer, Boolean> removeList = new HashMap<Integer, Boolean>();
			for(int i=0;i<hp.size();i++) {
				boolean isConflict = false;
				for(String pd : pds) {
					if(conflict(pd, hp.get(i), tablename)) {
						isConflict = true;
						break;
					}
				}
				if(isConflict) {
					removeList.put(i, true);
				}else {
					removeList.put(i, false);
				}
			}
			Set<Integer> keys = removeList.keySet();
			Vector<TreeNode> newSons = new Vector<TreeNode>();
			Vector<String> newPds = new Vector<String>();
			for(Integer i : keys) {
				if(! removeList.get(i)) {
					newSons.add(sroot.getChild().get(i));
					newPds.add(sroot.getPredicate().get(i));
				}
			}
			sroot.setChild(newSons);
			sroot.setPredicates(newPds);
			if(sroot.getChild().isEmpty()) {
				return true;
			}
			if(sroot.getChild().size() == 1) {
				TreeNode parent = sroot.getParent();
				Vector<TreeNode> sublings = parent.getChild();
				TreeNode son = sroot.getChild().get(0);
				sroot.getChild().clear();
				for(int i=0;i<sublings.size();i++) {
					if(sublings.get(i) == sroot) {
						sublings.set(i, son);
						son.setParent(parent);
						break;
					}
				}
				insertSelect(son, pds, tablename);
			}else {
				for(TreeNode child : sroot.getChild()) {
					insertSelect(child, pds, tablename);
				}
			}
		}else {//Must be Join
			HashMap<Integer, Vector<String>> goMap =
				new HashMap<Integer, Vector<String>>();
			for(String pd : pds) {
				pd = pd.split("[vV]")[0].trim();
				String attribute = 
					pd.split(op, 2)[0].trim().split("\\.", 2)[1].trim();
				Vector<String> tcs = sroot.getPredicate();
				for(int i=0;i<tcs.size();i++) {
					if(tcs.get(i).indexOf(attribute) != -1) {
						Vector<String> sonPds = goMap.get(i);
						if(sonPds == null) {
							sonPds = new Vector<String>();
						}
						sonPds.add(pd);
					}
				}
			}
			Set<Integer> set = goMap.keySet();
			for(Integer index : set) {
				TreeNode child = sroot.getChild().get(index);
				insertSelect(child, goMap.get(index), tablename);
			}
		}
		return false;
	}
	
	private void upLocallJoin(TreeNode ljroot) {
		return;
	}
	
	private void downProject(TreeNode proot) {
		return;
	}
	
	private void upUnion(TreeNode uroot) {
		while(uroot.getParent() != null) {
			TreeNode parent = uroot.getParent();
			String type = parent.getType();
			if(type.equals(Constant.DDB_LABEL_UNION)) {
				int size = uroot.getChild().size();
				Vector<TreeNode> brothers = parent.getChild();
				for(int i=0;i<brothers.size();i++) {
					if(brothers.get(i) == uroot) {
						brothers.remove(i);
						break;
					}
				}
				for(int i=0;i<size;i++) {
					TreeNode one = uroot.getChild().get(i);
					one.setParent(parent);
					parent.addChild(one);
					parent.addPredicate(uroot.getPredicate().get(i));
				}
				uroot.getChild().clear();
			}else if(type.equals(Constant.DDB_LABEL_PROJECT)) {
				TreeNode ancestor = parent.getParent();
				if(ancestor != null) {
					for(int i=0;i<ancestor.getChild().size();i++) {
						if(ancestor.getChild().get(i) == parent) {
							ancestor.getChild().set(i, uroot);
						}
					}
				}
				uroot.setParent(ancestor);
				Vector<TreeNode> children = uroot.getChild();
				for(int i=0;i<children.size();i++) {
					TreeNode one = new TreeNode();
					one.setType(Constant.DDB_LABEL_PROJECT);
					for(String e : parent.getAttribute()) {
						one.addAttribute(e);
					}
					TreeNode child = children.get(i);
					children.set(i, one);
					one.setParent(uroot);
					one.addChild(child);
					child.setParent(one);
				}
			}else {//Must be join
				TreeNode ancestor = parent.getParent();
				if(ancestor != null) {
					for(int i=0;i<ancestor.getChild().size();i++) {
						if(ancestor.getChild().get(i) == parent) {
							ancestor.getChild().set(i, uroot);
						}
					}
				}
				uroot.setParent(ancestor);
				int pos = -1;
				for(int i=0;i<parent.getChild().size();i++) {
					if(parent.getChild().get(i) == uroot) {
						pos = i;
						break;
					}
				}
				Vector<TreeNode> newSons = new Vector<TreeNode>();
				for(int i=0;i<uroot.getChild().size();i++) {
					TreeNode one = parent.clone();
					one.getChild().set(pos, uroot.getChild().get(i));
					one.setParent(uroot);
					newSons.add(one);
				}
				uroot.setChild(newSons);
			}
		}
	}
	
	private ArrayList<TreeNode> collector(TreeNode root, String label) {
		ArrayList<TreeNode> list = new ArrayList<TreeNode>();
		ConcurrentLinkedQueue<TreeNode> tqueue = 
			new ConcurrentLinkedQueue<TreeNode>();
		tqueue.add(root);
		while(! tqueue.isEmpty()) {
			TreeNode head = tqueue.poll();
			if(head.getType().equals(label)) {
				list.add(head);
			}
			for(TreeNode child : head.getChild()) {
				tqueue.add(child);
			}
		}
		return list;
	}
	
	public TreeNode optimize() {
		TreeNode gs = root.getChild().get(0); //global select
		root.getChild().clear();
		root.addChild(gs.getChild().get(0));
		gs.getChild().clear();
		Vector<String> pds = gs.getPredicate();
		for(TreeNode subRoot : globelTable) {
			Vector<String> newpds = new Vector<String>();
			for(String pd : pds) {
				String lv = pd.split(op, 2)[0].trim();
				if(lv.equalsIgnoreCase(subRoot.getName())) {
					newpds.add(pd);
				}
			}
			insertSelect(subRoot, newpds, subRoot.getName());
		}
		ArrayList<TreeNode> uList = collector(root, Constant.DDB_LABEL_UNION);
		for(TreeNode unode : uList) {
			upUnion(unode);
		}
		ArrayList<TreeNode> jList = collector(root, Constant.DDB_LABEL_JOIN);
		for(TreeNode jnode : jList) {
			upLocallJoin(jnode);
		}
		downProject(root);
		return root;
	}
	
	public List<String> getQueryQueue() {
		List<String> qList = new ArrayList<String>();
		return qList;
	}
	
	public void showTree(final TreeNode root) {
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				JFrame frame = new JFrame();
				frame.getContentPane().add(new MyComponent(root));
				frame.setSize(500, 500);
				frame.setVisible(true);
			}
		});
	}
	
	class MyComponent extends JComponent {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		private TreeNode root;
		
		public MyComponent(TreeNode root) {
			this.root = root;
		}

		public void paint(Graphics g) {
			String family = "Serif";
	        int style = Font.PLAIN;
	        int size = 18;
	        Font font = new Font(family, style, size);
	        g.setFont(font);
			printTree(root, g, 10);
		}
	}
	 
}
